/*
 * Copyright 2010 Alessandro Bellucci
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package org.soupframework.gwt.designerpanel.client.designer.impl;

import java.util.ArrayList;
import java.util.List;

import org.soupframework.gwt.designerpanel.client.command.Command;
import org.soupframework.gwt.designerpanel.client.designer.ContainerDesigner;
import org.soupframework.gwt.designerpanel.client.designer.Designer;
import org.soupframework.gwt.designerpanel.client.designer.DesignerManager;
import org.soupframework.gwt.designerpanel.client.designer.OperatingMode;
import org.soupframework.gwt.designerpanel.client.dragproxy.DragProxy;
import org.soupframework.gwt.designerpanel.client.event.ChildDesignerAddedEvent;
import org.soupframework.gwt.designerpanel.client.event.ChildDesignerAddedEventHandler;
import org.soupframework.gwt.designerpanel.client.event.ObjectAddedEvent;
import org.soupframework.gwt.designerpanel.client.event.ObjectAddedEventHandler;
import org.soupframework.gwt.designerpanel.client.event.ObjectRemovedEvent;
import org.soupframework.gwt.designerpanel.client.event.ObjectRemovedEventHandler;
import org.soupframework.gwt.designerpanel.client.event.ObjectSelectionChangedEvent;
import org.soupframework.gwt.designerpanel.client.event.ObjectSelectionChangedEventHandler;
import org.soupframework.gwt.designerpanel.client.util.Util;

import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.ui.Panel;
import com.google.gwt.user.client.ui.Widget;

public abstract class ContainerDesignerBase<T> extends DesignerBase<T> implements ContainerDesigner<T> {

	/**
	 * Children objects
	 */
	private List<T> childrenObjects = new ArrayList<T>();

	/**
	 * Children designers of this designer
	 */
	private List<Designer<T>> children = new ArrayList<Designer<T>>();

	/**
	 * Interactive objects
	 */
	private List<T> nonInteractiveObjects = new ArrayList<T>();

	/**
	 * List of selected objects
	 */
	private List<T> selectedObjects = new ArrayList<T>();

	/**
	 * Active {@link DragProxy}.
	 */
	private DragProxy<T> dragProxy;

	public ContainerDesignerBase(DesignerManager<T> manager, ContainerDesigner<T> parent, T object) {
		super(manager, parent, object);
	}

	public abstract class Operations extends DesignerBase<T>.Operations implements ContainerDesigner.Operations<T> {
		@Override
		public void addObject(T object, int left, int top) {
			childrenObjects.add(object);
		}

		@Override
		public void addObject(T object, int left, int top, int index) {
			childrenObjects.add(index, object);
		}

		@Override
		public void resizeObjectView(T object, int width, int height) {
			getChildObjectView(object).setWidth(width + "px");
			getChildObjectView(object).setHeight(height + "px");
		}

		@Override
		public void removeObject(T object) {
			Designer<T> d = getChildDesigner(object);
			if(d != null){
				children.remove(d);
			}
			if(nonInteractiveObjects.contains(object)){
				nonInteractiveObjects.remove(object);
			}
			if(selectedObjects.contains(object)){
				selectedObjects.remove(object);
			}
			childrenObjects.remove(object);
		}

		@Override
		public void exchangeObjects(T object1, T object2){
			int i1 = childrenObjects.indexOf(object1);
			childrenObjects.remove(i1);
			int i2 = childrenObjects.indexOf(object2);
			childrenObjects.add(i2, object1);
			childrenObjects.remove(i2 + 1);
			childrenObjects.add(i1, object2);
		}
	}

	@Override
	public List<T> getChildrenObjects() {
		return this.childrenObjects;
	}

	@Override
	public boolean hasChildObject(T object) {
		return this.childrenObjects.contains(object);
	}

	@Override
	public void moveFromContainer(ContainerDesigner<T> sourceDesigner, T object, int left, int top) {
		Command command = this.getManager().getCommandFactory().createMoveFromContainerCommand(this, sourceDesigner, object, left, top);
		this.getManager().doCommand(command);
	}

	@Override
	public void addObject(T object, int left, int top){
		Command command = this.getManager().getCommandFactory().createAddObjectCommand(this, object, left, top);
		this.getManager().doCommand(command);
	}

	@Override
	public void resizeObject(T object, int width, int height){
		Command command = this.getManager().getCommandFactory().createResizeObjectViewCommand(this, object, width, height);
		this.getManager().doCommand(command);
	}

	@Override
	public void resizeObjectPartial(String commandGroupTypePrefix, T object, int width, int height){
		Command command = this.getManager().getCommandFactory().createResizeObjectViewCommand(this, object, width, height);
		this.getManager().doPartialCommand("CommandGroupTypeResize-" + commandGroupTypePrefix + object.hashCode(), command);
	}

	@Override
	public void removeObject(T object){
		Command command = this.getManager().getCommandFactory().createRemoveObjectCommand(this, object);
		this.getManager().doCommand(command);
	}

	@Override
	public void clear(){
		Command command = this.getManager().getCommandFactory().createClearCommand(this);
		this.getManager().doCommand(command);
	}

	@Override
	public List<Designer<T>> getChildrenDesigners() {
		return new ArrayList<Designer<T>>(this.children);
	}

	@Override
	public Designer<T> getChildDesigner(T object) {
		for(Designer<T> d : this.children){
			if(object == d.getObject()){
				return d;
			}
		}
		return null;
	}

	@Override
	public void removeChildDesigner(Designer<T> childDesigner) {
		this.children.remove(childDesigner);
	}

	@Override
	public void addChildDesigner(Designer<T> childDesigner){
		if(!hasChildObject(childDesigner.getObject())){
			throw new IllegalArgumentException("Cannot add child designer " + childDesigner + ": " + childDesigner.getObject() + " is not a child object");
		}
		((DesignerBase<T>)childDesigner).setParent(this);
		this.children.add(childDesigner);
		childDesigner.refresh();
	}

	@Override
	public void registerChildObject(T object) {
		this.childrenObjects.add(object);
	}

	@Override
	public boolean isDescendantDesigner(Designer<T> designer){
		for(Designer<T> d : this.getChildrenDesigners()){
			if(d == designer){
				return true;
			}
		}
		for(ContainerDesigner<T> cd : this.getChildrenContainerDesigners()){
			if(cd.isDescendantDesigner(designer)){
				return true;
			}
		}
		return false;
	}

	@Override
	public HandlerRegistration addObjectAddedHandler(ObjectAddedEventHandler<T> handler) {
		return addHandler(handler, ObjectAddedEvent.<T>getType());
	}

	@Override
	public HandlerRegistration addObjectRemovedHandler(ObjectRemovedEventHandler<T> handler) {
		return addHandler(handler, ObjectRemovedEvent.<T>getType());
	}

	@Override
	public HandlerRegistration addChildDesignerAddedHandler(ChildDesignerAddedEventHandler<T> handler) {
		return addHandler(handler, ChildDesignerAddedEvent.<T>getType());
	}

	@Override
	public HandlerRegistration addObjectSelectionChangedHandler(ObjectSelectionChangedEventHandler<T> handler) {
		return addHandler(handler, ObjectSelectionChangedEvent.<T>getType());
	}

	/**
	 * Sets whether the object must be interactive (in the {@link Designer} sense) or not.
	 *
	 * @param object The object
	 * @param interactive true if the object is to be interactive
	 */
	public void setInteractive(T object, boolean interactive){
		if(this.getChildrenObjects().contains(object)){
			if(interactive == true && nonInteractiveObjects.contains(object)){
				nonInteractiveObjects.remove(object);
			}
			else if(interactive == false && !nonInteractiveObjects.contains(object)){
				nonInteractiveObjects.add(object);
			}
		}
	}

	/**
	 * Returns the list of interactive objects.
	 *
	 * @return The list of interactive objects
	 */
	public List<T> getInteractiveObjects(){
		List<T> interactiveObjects = new ArrayList<T>();
		for(T object : this.getChildrenObjects()){
			if(!nonInteractiveObjects.contains(object)){
				interactiveObjects.add(object);
			}
		}
		return interactiveObjects;
	}

	@Override
	public void clearSelection(){
		clearSelection(true);
	}

	@Override
	public void clearSelection(boolean fireEvent){
		clearSelection(fireEvent, true);
	}

	@Override
	public void clearSelection(boolean fireEvent, boolean deep){
		if(deep){
			for(ContainerDesigner<T> cd : this.getChildrenContainerDesigners()){
				cd.clearSelection(fireEvent);
			}
		}

		for(T object : this.selectedObjects){
			dehighlightObject(object);
			//hideResizePoints(w);
		}
		if(this.selectedObjects.size() > 0){
			this.selectedObjects.clear();
			if(fireEvent){
				fireEvent(new ObjectSelectionChangedEvent<T>(getSelectedObjects(false)));
			}
		}
	}

	@Override
	public void setSelection(Iterable<T> os){
		setSelection(os, true);
	}

	@Override
	public void setSelection(Iterable<T> os, boolean fireEvent){
		clearSelection(false);
		addObjectsToSelection(os, false);
		if(fireEvent){
			fireEvent(new ObjectSelectionChangedEvent<T>(getSelectedObjects(false)));
		}
	}

	@Override
	public boolean isObjectSelected(T object){
		return selectedObjects.contains(object);
	}

	@Override
	public boolean areObjectsSelected(Iterable<T> os){
		for(T object : os){
			if(!isObjectSelected(object)){
				return false;
			}
		}
		return true;
	}

	@Override
	public void removeObjectFromSelection(T object){
		removeObjectFromSelection(object, true);
	}

	@Override
	public void removeObjectFromSelection(T object, boolean fireEvent){
		if(isObjectSelected(object)){
			dehighlightObject(object);
			//hideResizePoints(object);
			this.selectedObjects.remove(object);
			if(fireEvent){
				fireEvent(new ObjectSelectionChangedEvent<T>(getSelectedObjects(false)));
			}
		}
	}

	@Override
	public void removeObjectsFromSelection(Iterable<T> os){
		removeObjectsFromSelection(os, true);
	}

	@Override
	public void removeObjectsFromSelection(Iterable<T> os, boolean fireEvent){
		if(os.iterator().hasNext()){
			for(T object : os){
				removeObjectFromSelection(object, false);
			}
			fireEvent(new ObjectSelectionChangedEvent<T>(getSelectedObjects(false)));
		}
	}

	@Override
	public void addObjectToSelection(T object){
		addObjectToSelection(object, true);
	}

	@Override
	public void addObjectToSelection(T object, boolean fireEvent){
		if(this.selectedObjects.contains(object)){
			// widget was already selected
			return;
		}

		if(this.getInteractiveObjects().contains(object)){
			highlightObject(object);
			//showResizePoints(object);
			this.selectedObjects.add(object);
			if(fireEvent){
				fireEvent(new ObjectSelectionChangedEvent<T>(getSelectedObjects(false)));
			}
		}
	}

	@Override
	public void addObjectsToSelection(Iterable<T> os){
		addObjectsToSelection(os, true);
	}

	@Override
	public void addObjectsToSelection(Iterable<T> os, boolean fireEvent){
		if(os.iterator().hasNext()){
			for(T object : os){
				addObjectToSelection(object, false);
			}
			fireEvent(new ObjectSelectionChangedEvent<T>(getSelectedObjects(false)));
		}
	}

	@Override
	public void toggleObjectsSelection(Iterable<T> os){
		toggleObjectsSelection(os, true);
	}

	@Override
	public void toggleObjectsSelection(Iterable<T> os, boolean fireEvent){
		for(T object : os){
			if(isObjectSelected(object)){
				removeObjectFromSelection(object, false);
			}
			else{
				addObjectToSelection(object, false);
			}
		}
		if(fireEvent){
			fireEvent(new ObjectSelectionChangedEvent<T>(getSelectedObjects(false)));
		}
	}

	@Override
	public int getSelectedObjectsCount(boolean deep){
		int count = getSelectedObjectsCount();
		if(deep){
			for(ContainerDesigner<T> cd : this.getChildrenContainerDesigners()){
				count += cd.getSelectedObjectsCount(true);
			}
		}
		return count;
	}

	protected int getSelectedObjectsCount(){
		return this.selectedObjects != null ? this.selectedObjects.size() : 0;
	}

	@Override
	public List<T> getSelectedObjects(boolean deep){
		if(!deep){
			return new ArrayList<T>(this.selectedObjects);
		}
		else{
			List<T> ret = new ArrayList<T>(this.selectedObjects);
			for(ContainerDesigner<T> cd : getChildrenContainerDesigners()){
				ret.addAll(cd.getSelectedObjects(true));
			}
			return ret;
		}
	}

	@SuppressWarnings("unchecked")
	@Override
	public List<ContainerDesigner<T>> getChildrenContainerDesigners(){
		List<ContainerDesigner<T>> ret = new ArrayList<ContainerDesigner<T>>();
		for(Designer<T> d : children){
			if(d instanceof ContainerDesigner){
				ret.add((ContainerDesigner<T>)d);
			}
		}
		return ret;
	}

	@Override
	public Iterable<T> getObjectsAtCoords(int x, int y){
		List<T> ret = new ArrayList<T>();
		for(T object : this.getInteractiveObjects()){
			if(Util.widgetContainsCoords(getChildObjectView(object), x, y)){
				ret.add(object);
			}
		}
		return ret;
	}

	@Override
	public T getForemostObjectAtCoords(int x, int y){
		for(int i = this.getInteractiveObjects().size() - 1 ; i >= 0 ; -- i){
			T object = this.getInteractiveObjects().get(i);
			if(Util.widgetContainsCoords(getChildObjectView(object), x, y)){
				return object;
			}
		}
		return null;
	}

	@Override
	public Iterable<T> getObjectsInArea(int startX, int startY, int endX, int endY){
		List<T> ret = new ArrayList<T>();
		for(T object : this.getInteractiveObjects()){
			if(Util.widgetIntersectsArea(getChildObjectView(object), startX, startY, endX, endY)){
				ret.add(object);
			}
		}
		return ret;
	}

	@Override
	public void openSelectionContextMenu(int preferredLeft, int preferredTop){
		if(this.getManager().getContextMenuFactory() != null){
			List<T> objects = this.getSelectedObjects(false);
			Panel contextMenu = this.getManager().getContextMenuFactory().getContextMenu(objects);
			this.getManager().getDesignerPanel().showContextMenu(objects, contextMenu, preferredLeft, preferredTop);
		}
	}

	/**
	 * Sets the object's width. Sets the glass panel and grid width too.
	 */
	@Override
	public void setWidth(String width) {
	/*	this.getWidget().setWidth(width);
		DOM.setStyleAttribute(this.glassPanel, "width", width);
		if(this.grid != null){
			this.grid.setPixelWidth(getStyleValueInPixel(width));
		}*/
	}

	/**
	 * Sets the object's height. Sets the glass panel and grid height too.
	 */
	@Override
	public void setHeight(String height) {
	/*	super.getWidget().setHeight(height);
		DOM.setStyleAttribute(this.glassPanel, "height", height);
		if(this.grid != null){
			this.grid.setPixelHeight(getStyleValueInPixel(height));
		}*/
	}

	protected void highlightObject(T object){
		if(this.getManager().getObjectHighlighter() != null){
			this.getManager().getObjectHighlighter().decorateObject(object, getChildObjectView(object));
		}
	}

	protected void dehighlightObject(T object){
		if(this.getManager().getObjectHighlighter() != null){
			this.getManager().getObjectHighlighter().undecorateObject(object, getChildObjectView(object));;
		}
	}

	protected void showResizePoints(T object){
		if(this.getManager().getResizePointsDecorator() != null){
			this.getManager().getResizePointsDecorator().decorateObject(object, getChildObjectView(object));
		}
	}

	protected void hideResizePoints(T object){
		if(this.getManager().getResizePointsDecorator() != null){
			this.getManager().getResizePointsDecorator().undecorateObject(object, getChildObjectView(object));
		}
	}

	@Override
	public final void showDragProxy(T object, Widget objectView, int left, int top){
		if(this.dragProxy != null && !dragProxy.getProxiedObject().equals(object)){
			hideDragProxy();
			dragProxy = null;
		}

		if(this.getManager().getDragProxyFactory() != null){
			if(dragProxy == null){
				dragProxy = this.getManager().getDragProxyFactory().createDragProxy(object, objectView);			
			}
			showDragProxyInt(dragProxy, left, top);
		}
	}

	protected abstract void showDragProxyInt(DragProxy<T> dragProxy, int left, int top);

	@Override
	public final void hideDragProxy(){
		if(dragProxy != null){
			hideDragProxyInt(dragProxy);
			dragProxy = null;
		}
	}

	protected abstract void hideDragProxyInt(DragProxy<T> dragProxy);

	@Override
	public boolean isDropAllowed(T object, int left, int top) {
		return true;
	}

	@Override
	public boolean acceptDrop(T object, int left, int top) {
		return true;
	}

	@Override
	public void refresh() {
		super.refresh();

		if(this.getManager().getContainerDesignModeDecorator() != null){
			if(this.getManager().getOperatingMode() == OperatingMode.DESIGN_MODE){
				this.getManager().getContainerDesignModeDecorator().decorateObject(this.getObject(), this.getObjectView());
			}
			else{
				this.getManager().getContainerDesignModeDecorator().undecorateObject(this.getObject(), this.getObjectView());
			}
		}

		for(Designer<T> d : getChildrenDesigners()){
			d.refresh();
		}
	}
}
